package src

import (
	"errors"
	"fmt"
	"sync"

	"github.com/gorilla/websocket"
	"github.com/sirupsen/logrus"
)

// Client is a middleman between the websocket connection and the hub.
type Client struct {
	hub *Hub

	// The websocket connection.
	conn *websocket.Conn

	// Buffered channel of outbound messages.
	send chan []byte
	// 客户端唯一连接标识符，程序内部设置
	// 唯一性由len(clients)保证
	id string
	// 已加入的房间列表
	rooms sync.Map
	// 用户自定义连接标识符，由用户来保证唯一
	// 该用户标识只有在用户断开连接的时候才删除
	userId string
	// 附加数据 再回调中原样返回
	attach string
	// 客户端彻底关闭的通道，当客户端彻底关闭后，会向此通道发送一条数据
	closed chan bool
	// 读泵是否已经停止
	readPumpStopped chan bool
	// 写泵是否已经停止
	writePumpStopped chan bool
}

// IClientManager 房间接口，包含获取所有客户端、加入客户端、移除客户端功能
type IClientManager interface {
	// All 获取所有客户端
	All() []*Client
	// Add 添加一个客户端
	Add(c *Client)
	// Remove 移除一个客户端
	Remove(c *Client)
	// Has 检查是否存在一个客户端
	Has(c *Client) bool
	// Join 将一个客户端加入房间
	Join(c *Client, r *Room)
	// Leave 让一个客户端离开房间
	Leave(c *Client, roomId string)
	// LeaveAll 让一个客户端离开所有房间
	LeaveAll(c *Client)
	// Joined 检查某个客户端是否在某个房间内
	Joined(c *Client, roomId string) bool
	// Register 为某个客户端注册用户标识符
	Register(c *Client, userId string) error
	// UnRegister 删除某个客户端的用户标识符
	UnRegister(c *Client)
	// Registered 是否有注册了某个标识的用户
	Registered(userId string) bool
	// Push 向某个用户推送消息
	Push(userId string, message []byte)
	// AllRegistered 获取所有已注册的客户端
	AllRegistered() []*Client
}

type IClient interface {
	// Join 客户端加入房间
	Join(room *Room)
	// Leave 客户端离开房间
	Leave(roomId string)
	// Joined 客户端是否已经加入了该房间
	Joined(roomId string) bool
	// Rooms 当前客户端所加入的所有房间列表
	Rooms() []*Room
	// Register 为客户端注册自定义标识符，由用户保证标识符的唯一
	Register(userId string)
}

type ClientManager struct {
	clients           sync.Map
	registeredClients sync.Map
}

var (
	ErrUserIdConflicts = errors.New("user id conflicts")
	cm                 *ClientManager
)

func NewClient(hub *Hub, conn *websocket.Conn, cid string, attach string) *Client {
	return &Client{
		hub:              hub,
		conn:             conn,
		send:             make(chan []byte, 256),
		id:               cid,
		attach:           attach,
		rooms:            sync.Map{},
		userId:           "",
		closed:           make(chan bool, 1), // 使用缓冲channel的原因是刷新页面断开连接时，接收方可能不在会导致阻塞
		readPumpStopped:  make(chan bool, 1),
		writePumpStopped: make(chan bool, 1),
	}
}

// Refresh 更新客户端，主要是更新连接和重设send channel。
func (c *Client) Refresh(conn *websocket.Conn) {
	// 退出所有房间
	c.hub.clientManager.LeaveAll(c)
	c.hub.clientManager.Remove(c)
	c.hub.clientManager.UnRegister(c)
	c.conn = conn
	c.attach = ""
	c.send = make(chan []byte, 256)
	c.closed = make(chan bool, 1)
	c.readPumpStopped = make(chan bool, 1)
	c.writePumpStopped = make(chan bool, 1)
}

func (c *Client) Join(r *Room) {
	logrus.WithFields(logrus.Fields{"location": "client", "method": "join"}).Info("Join room " + r.id)
	if c.Joined(r.id) {
		return
	}
	c.rooms.Store(r.id, r)
}

func (c *Client) Leave(roomId string) {
	logrus.WithFields(logrus.Fields{"location": "client", "method": "leave"}).Info("Leave room " + roomId)
	if !c.Joined(roomId) {
		return
	}
	c.rooms.Delete(roomId)
}

func (c *Client) Joined(roomId string) bool {
	_, ok := c.rooms.Load(roomId)
	return ok
}

func (c *Client) Rooms() []*Room {
	var rms []*Room
	c.rooms.Range(func(key, value interface{}) bool {
		rms = append(rms, value.(*Room))
		return true
	})
	return rms
}

func (c *Client) Register(userId string) {
	c.userId = userId
	logrus.WithFields(logrus.Fields{"location": "client", "method": "Register"}).Debug(fmt.Sprintf("Client %s registered as %s", c.id, c.userId))
	// 用户注册成功，进行报告
	go Report(c.hub, "register", c.id, userId, c.attach)
}

func NewClientManager() *ClientManager {
	onceCM.Do(func() {
		cm = &ClientManager{
			clients:           sync.Map{},
			registeredClients: sync.Map{},
		}
	})
	return cm
}

func (cm *ClientManager) All() []*Client {
	var clts []*Client
	cm.clients.Range(func(key, value interface{}) bool {
		clts = append(clts, value.(*Client))
		return true
	})
	return clts
}

func (cm *ClientManager) Add(c *Client) {
	logrus.WithFields(logrus.Fields{"location": "client manager", "method": "add"}).Info("Add client " + c.id)
	if cm.Has(c) {
		return
	}
	cm.clients.Store(c.id, c)
}

func (cm *ClientManager) Remove(c *Client) {
	logrus.WithFields(logrus.Fields{"location": "client manager", "method": "remove"}).Info("Remove client " + c.id)
	if !cm.Has(c) {
		return
	}
	cm.clients.Delete(c.id)
}

func (cm *ClientManager) Has(c *Client) bool {
	_, ok := cm.clients.Load(c.id)
	return ok
}

// Join 客户端加入某个房间，先由房间管理器将这个客户端加入房间，然后这个客户端再记录下自己所加入的房间
func (cm *ClientManager) Join(c *Client, r *Room) {
	logrus.WithFields(logrus.Fields{"location": "client manager", "method": "join"}).Info("Join client " + c.id + " to room " + r.id)
	c.hub.roomManager.IntroduceClient(c, r)
	c.Join(r)
}

// Leave 客户端离开某个房间，先由房间管理器将这个客户端从房间中踢出去，然后这个客户端再清除该房间记录
func (cm *ClientManager) Leave(c *Client, roomId string) {
	logrus.WithFields(logrus.Fields{"location": "client manager", "method": "leave"}).Info("Client " + c.id + " leave from room " + roomId)
	r := c.hub.roomManager.Pick(roomId)
	if r == nil {
		return
	}
	c.hub.roomManager.KickOutClient(c, r)
	c.Leave(roomId)
}

// LeaveAll 客户端离开所有房间
func (cm *ClientManager) LeaveAll(c *Client) {
	logrus.WithFields(logrus.Fields{"location": "client manager", "method": "leaveAll"}).Info("Client " + c.id + " leave from all rooms")
	for _, r := range c.Rooms() {
		c.hub.roomManager.KickOutClient(c, r)
		c.Leave(r.id)
	}
}

// Joined 检查某个客户端是否在某个房间内
func (cm *ClientManager) Joined(c *Client, roomId string) bool {
	return c.Joined(roomId)
}

func (cm *ClientManager) Register(c *Client, userId string) error {
	if c.userId == userId {
		return nil
	}
	if _, ok := cm.registeredClients.Load(userId); ok {
		// 已经有人用了这个标识符
		return ErrUserIdConflicts
	}
	if c.userId == "" {
		cm.registeredClients.Store(userId, c)
		c.Register(userId)
		return nil
	}
	// 这里是更换用户标识了
	cm.registeredClients.Delete(c.userId)
	cm.registeredClients.Store(userId, c)
	c.Register(userId)
	return nil
}

func (cm *ClientManager) UnRegister(c *Client) {
	// 客户端从hub中直接移除了，其自带的userId不需要主动释放
	// 这里只从客户端管理器的已注册客户端map中将其删除
	cm.registeredClients.Delete(c.userId)
	c.userId = ""
}

func (cm *ClientManager) Registered(userId string) bool {
	_, ok := cm.registeredClients.Load(userId)
	return ok
}

func (cm *ClientManager) Push(userId string, message []byte) {
	c, ok := cm.registeredClients.Load(userId)
	if ok {
		c.(*Client).send <- message
	} else {
		// 这里是根据默认连接标识进行推送
		c, ok = cm.clients.Load(userId)
		if ok {
			c.(*Client).send <- message
		}
	}
}

func (cm *ClientManager) AllRegistered() []*Client {
	var rclts []*Client
	cm.registeredClients.Range(func(key, value interface{}) bool {
		rclts = append(rclts, value.(*Client))
		return true
	})
	return rclts
}
